
import java.io.IOException;
import java.io.PrintStream;
import java.net.Socket;



public class LamportMutex {
	VectorClock vectorClock; // my vector clock
	int[] requests; // CS request queue
	int myId; // my serverid
	int numServers = Constants.NUM_SERVERS;
	boolean[] serversStatus; //which servers are up 

	public LamportMutex(int id) {
		myId = id;
		vectorClock = new VectorClock(id);
		requests = new int[numServers];
		serversStatus = new boolean[numServers];
		for (int j = 0; j < numServers; j++){
			requests[j] = Constants.INFINITY;
			serversStatus[j] = false;
		}
		serversStatus[id] = true;
	}

	// helper method to request CS lock
	public synchronized void requestCS() {
		vectorClock.tick();
		requests[myId] = vectorClock.getValue(myId);
		broadcastMsg(Constants.LOCK_REQUEST, requests[myId]);
		while (!okayCS()) {
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	// helper method to release CS lock
	public synchronized void releaseCS() {
		requests[myId] = Constants.INFINITY;
		broadcastMsg(Constants.LOCK_RELEASE, vectorClock.getValue(myId));
	}

	// helper method to determine if I have access to CS
	private boolean okayCS() {
		for (int j = 0; j < numServers; j++) {
			if(serversStatus[j]){
				if (isGreater(requests[myId], myId, requests[j], j))
					return false;
				if (isGreater(requests[myId], myId, vectorClock.getValue(j), j))
					return false;
			}
		}
		return true;
	}

	// helper method to determine which CS request is greater
	private boolean isGreater(int entry1, int id1, int entry2, int id2) {
		if (entry2 == Constants.INFINITY)
			return false;
		return ((entry1 > entry2) || ((entry1 == entry2) && (id1 > id2)));
	}

	// this function handles messages related to mutual exclusion locking
	public synchronized void handleMsg(String requestString) {
		String[] request = requestString.split(" ");
		int senderId = Integer.valueOf(request[1]);
		int recipientId = Integer.valueOf(request[2]);
		int senderTimeStamp = Integer.valueOf(request[3]);
		String senderTag = request[4];
		vectorClock.receiveAction(senderId, senderTimeStamp);
		serversStatus[recipientId] = true;
		if (senderTag.equals(Constants.LOCK_REQUEST)) {
			requests[senderId] = senderTimeStamp;
			sendMsg(senderId, Constants.LOCK_ACK, vectorClock.getValue(myId));
		} else if (senderTag.equals(Constants.LOCK_RELEASE)) {
			requests[senderId] = Constants.INFINITY;
		}
		notify(); // okayCS() may be true now
	}

	private synchronized void broadcastMsg(String tag, int timeStamp) {
		for (int i = 0; i < numServers; i++) {
			if (i != myId)
				sendMsg(i, tag, timeStamp);
		}
	}

	private synchronized void sendMsg(int recipientId, String tag, int timeStamp) {
		Socket socket;
		try {
			socket = new Socket(Constants.HOSTNAME, Constants.getPort(recipientId));
			PrintStream printStream = new PrintStream(socket.getOutputStream());
			printStream.println(Constants.LOCK_MESSAGE + " " + myId + " " + recipientId + " " + timeStamp + " " + tag);
			printStream.flush();
			socket.close();
		} catch(java.net.ConnectException e){
			serversStatus[recipientId] = false;
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

}
